/*  This file is part of the comicconquer project
    Copyright (C) 2010 Reach <reach334@gmail.com>

    ComicConquer is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This software is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public 
    License along with this software.  If not, see <http://www.gnu.org/licenses/>.

*/

#include "ProcessThread.h"
#include "QIplImage.h"
#include "onyx/ui/message_dialog.h"

extern QDialog* g_mainDialog;

/**
 * Constructor.
 * 
 * @author reach (5/26/2010)
 * 
 * @param parent: Parent widget.
 */
ProcessThread::ProcessThread(QObject* parent)
: QThread(parent)
{
    In_Log(__FUNCTION__, COCLASSNAME);

    m_zipContainer = NULL;
    m_restart = false;
    m_abort = false;
    m_request = false;
    m_isPrev = false;
    m_iPage = 0;
    m_imgProfile.option = Opt_None;
    m_imgProfile.maxMarginPercent = 10;

    Out_Log(__FUNCTION__, COCLASSNAME);
}

/**
 * Destructor. Abort the thread and release resources.
 * 
 * @author reach (5/26/2010)
 */
ProcessThread::~ProcessThread(){
    In_Log(__FUNCTION__, COCLASSNAME);

    m_mutex.lock(); //>> m_mutex lock
    m_abort = true;
    m_condition.wakeOne();
    m_mutex.unlock(); //<< m_mutex unlock

    wait();

    if (m_zipContainer != NULL)
    {
        delete m_zipContainer;
        m_zipContainer = NULL;
    }

    Out_Log(__FUNCTION__, COCLASSNAME);
}

/**
 * Load the comic on directory form.
 * 
 * @author reach (5/26/2010)
 * 
 * @param dir: QDir for the comic path.
 * @param fileNames: File names of all comic images.
 * @param imgProfile: Profile for image processing.
 * @param iPage: Initializing original page number.
 * @param iDivPage: Initializing divided page number.
 * 
 * @return bool: True for successfully loading.
 */
bool ProcessThread::LoadDir(QDir& dir, QStringList& fileNames, ImageProfile& imgProfile, int iPage, int iDivPage){
    In_Log(__FUNCTION__, COCLASSNAME);

    m_mutex.lock(); //>> m_mutex lock
    if (m_dir == dir)
    {
        m_mutex.unlock(); //<< m_mutex unlock
        Out_Log(__FUNCTION__, COCLASSNAME);
        return false; 
    }

    if (m_zipContainer != NULL)
    {
        delete m_zipContainer;
        m_zipContainer = NULL;
    }

    m_dir = dir;
    m_sFileNames = fileNames;
    m_iPageCount = fileNames.size();
    m_imgProfile = imgProfile;
    m_pageQueue.requestInit(m_iPageCount);
    m_iPage  = -1;
    m_mutex.unlock(); //<< m_mutex unlock

    RequestPage(iPage,iDivPage);

    Out_Log(__FUNCTION__, COCLASSNAME);
    return true;
}

/**
 * Load the comic on zip form.
 * 
 * @author reach (5/26/2010)
 * 
 * @param zipContainer: The ZipContainer contain the informations of the zip file to load.
 * @param imgProfile: Profile for image processing.
 * @param iPage: Initializing original page number.
 * @param iDivPage: Initializing divided page number.
 * @return bool: True for successfully loading.
 */
bool ProcessThread::LoadZip(ZipContainer* zipContainer, ImageProfile& imgProfile, int iPage, int iDivPage)
{
    In_Log(__FUNCTION__, COCLASSNAME);

    m_mutex.lock(); //>> m_mutex lock
    if (m_zipContainer != NULL && m_zipContainer->zipFileName() == zipContainer->zipFileName())
    {
        m_mutex.unlock(); //<< m_mutex unlock

        delete zipContainer;

        Out_Log(__FUNCTION__, COCLASSNAME);
        return false; 
    }

    if (m_zipContainer != NULL)
    {
        delete m_zipContainer;
        m_zipContainer = NULL;
    }

    m_zipContainer = zipContainer;

    m_dir.setPath(QString(""));
    m_iPageCount = zipContainer->count();
    m_imgProfile = imgProfile;
    m_pageQueue.requestInit(m_iPageCount);
    m_iPage  = -1;
    m_mutex.unlock(); //<< m_mutex unlock

    RequestPage(iPage,iDivPage);

    Out_Log(__FUNCTION__, COCLASSNAME);
    return true;
}

/**
 * Request the processing thread to process the page.
 * 
 * @author reach (5/26/2010)
 * 
 * @param iPage: Original page number. 
 * @param iDivPage: Divided page number.
 * @param isPrev: Whether it is turning page forward. 
 * 
 * @return DivPage*: If the requested page has processed, return it. Or return NULL.
 */
DivPage* ProcessThread::RequestPage(int iPage, int iDivPage, bool isPrev)
{
    In_Log(__FUNCTION__, COCLASSNAME);

    if (m_request)
    {
        Out_Log(__FUNCTION__, COCLASSNAME);
        return NULL; 
    }

    // Invalid page number.
    if (iPage < 0 || iPage >= m_iPageCount)
    {
        iPage = 0;
        iDivPage = 0; 
    }

    m_mutex.lock(); //>> m_mutex lock
    m_iPage = iPage;
    m_iDivPage = iDivPage;
    m_isPrev = isPrev;
    Page* page = m_pageQueue.getPage(m_iPage);
    m_pageQueue.moveTo(m_iPage);

    DivPage* divPage = NULL;
    m_request = true; // Need to send the requested divpage.

    // Whether it need to process the page.
    if (!m_pageQueue.isInitRequest() && !m_pageQueue.isReloadRequest() && page != NULL && page->loaded)
    {
        page->mutex.lock(); //>> page lock
        if (page->loaded)
        {
            m_iDivCount = page->divCount;
            m_iDivPage = CO_MIN(m_iDivPage, m_iDivCount - 1);

            if (page->divPage[m_iDivPage]->processed)
            {
                divPage = page->divPage[m_iDivPage];
                m_request = false;
            }
        }
        page->mutex.unlock(); //<< page unlock
    }

    m_mutex.unlock(); //<< m_mutex unlock

    if (!isRunning())
    {
        start(QThread::NormalPriority); 
    }
    else
    {
        m_mutex.lock(); //>> m_mutex lock.
        m_condition.wakeOne();
        m_mutex.unlock();
    }

    Out_Log(__FUNCTION__, COCLASSNAME);
    return divPage;
}

/**
 * Update the arguments of image processing to keep synchronized with the image processing profile. 
 * 
 * @author reach (5/26/2010)
 * 
 * @param imgProfile: The image processing profile.
 * @param iPage: Original page number. 
 * @param iDivPage: Divided page number.
 * 
 * @return bool: True indicate that the images need to be re-processed.
 */
bool ProcessThread::updateProfile(ImageProfile& imgProfile, int iPage, int iDivPage)
{
    In_Log(__FUNCTION__, COCLASSNAME);

    m_mutex.lock(); //>> m_mutex lock

    if (m_imgProfile == imgProfile)
    {
        m_mutex.unlock(); //<< m_mutex unlock
        Out_Log(__FUNCTION__, COCLASSNAME);
        return false; 
    }

    m_imgProfile = imgProfile;
    m_pageQueue.requestReload();

    m_mutex.unlock(); //<< m_mutex unlock

    RequestPage(iPage, iDivPage);

    Out_Log(__FUNCTION__, COCLASSNAME);
    return true;
}

void ProcessThread::reOpenZip()
{
    In_Log(__FUNCTION__, COCLASSNAME);

    m_zipContainer->reOpen();
    
    Out_Log(__FUNCTION__, COCLASSNAME);
}

/**
 * Load the page from image files or zip files.
 * And then divide it if need.
 * 
 * @author reach (5/26/2010)
 * 
 * @param page: The page struct to store the properties for original page.
 */
void ProcessThread::loadPage(Page& page)
{
    In_Log(__FUNCTION__, COCLASSNAME);

    DivPage& divPage1 = *page.divPage[0];
    DivPage& divPage2 = *page.divPage[1];

    divPage1.iPage = page.iPage;
    divPage2.iPage = page.iPage;

    divPage1.iPageCount = page.pageCount;
    divPage2.iPageCount = page.pageCount;

    divPage1.iDivPage = 0;
    divPage2.iDivPage = 1;

    /*********************load image****************/ 
    // Load from zip file.
    if (m_zipContainer != NULL)
    {
        divPage1.images[Cot_Origin] = m_zipContainer->getImage(page.iPage);

        if (divPage1.images[Cot_Origin] == NULL)
        {
            ui::MessageDialog dlg(QMessageBox::Warning, tr("错误"), tr("图片载入出错！"), QMessageBox::Ok);
            dlg.exec();
            QApplication::exit();

            Out_Log(__FUNCTION__, COCLASSNAME);
            return;
        }
    }
    // Load from image file.
    else
    {
        divPage1.images[Cot_Origin] = new QImage;

        m_mutex.lock(); //>> m_mutex lock
        QString fileName = m_dir.absoluteFilePath(m_sFileNames[page.iPage]);
        m_mutex.unlock(); //<< m_mutex unlock

        if (!divPage1.images[Cot_Origin]->load(fileName))
        {
            ui::MessageDialog dlg(QMessageBox::Warning, tr("错误"), tr("图片载入出错！"), QMessageBox::Ok);
            dlg.exec();
            QApplication::exit();

            Out_Log(__FUNCTION__, COCLASSNAME);
            return;
        }
    }

    /******************divide page************************/
    if (m_imgProfile.option & Opt_DividePage
        && (float)divPage1.images[Cot_Origin]->width() / divPage1.images[Cot_Origin]->height() >= 0.8f)
    {
        page.divCount = 2;
        divPage1.iDivPageCount = 2;
        divPage2.iDivPageCount = 2;

        divPage2.images[Cot_Origin] = new QImage;

        if (m_imgProfile.option & Opt_RightToLeft)
        {
            *divPage2.images[Cot_Origin]
            = divPage1.images[Cot_Origin]->copy(0,
                                                0,
                                                divPage1.images[Cot_Origin]->width()/2,
                                                divPage1.images[Cot_Origin]->height()); 

            *divPage1.images[Cot_Origin]
            = divPage1.images[Cot_Origin]->copy(divPage2.images[Cot_Origin]->width(),
                                                0,
                                                divPage1.images[Cot_Origin]->width() - divPage2.images[Cot_Origin]->width(),
                                                divPage1.images[Cot_Origin]->height());
        }
        else
        {
            *divPage2.images[Cot_Origin]
            = divPage1.images[Cot_Origin]->copy(divPage1.images[Cot_Origin]->width()/2,
                                                0,
                                                divPage1.images[Cot_Origin]->width() - divPage1.images[Cot_Origin]->width()/2,
                                                divPage1.images[Cot_Origin]->height());

            *divPage1.images[Cot_Origin]
            = divPage1.images[Cot_Origin]->copy(0,
                                                0,
                                                divPage1.images[Cot_Origin]->width()/2,
                                                divPage1.images[Cot_Origin]->height());
        }
    }
    else
    {
        page.divCount = 1;
        divPage1.iDivPageCount = 1;
        divPage2.iDivPageCount = 1;
    }

    page.loaded = true;

    Out_Log(__FUNCTION__, COCLASSNAME);
}

/**
 * Process divided page.
 * 
 * @author reach (5/26/2010)
 * 
 * @param divPage: The struct for divided page.
 */
void ProcessThread::processPage(DivPage& divPage)
{
    In_Log(__FUNCTION__, COCLASSNAME);

    // Analyze image if need.
    if (m_imgProfile.option & (Opt_CutMargin | Opt_Align | Opt_ScrollMode))
    {
        imageAnalyze(divPage);
    }

    /*******************scale image********************/
    // Set scale parameters.
    QSize ScrSize = g_mainDialog->rect().size();

    // Scale the image.
    divPage.images[Cot_Origin] = QIplImage::convertToGray(divPage.images[Cot_Origin]);

    divPage.images[Cot_Scaled] = new QImage;

    if (m_imgProfile.option & Opt_ScrollMode)
    {
        *divPage.images[Cot_Scaled] = divPage.images[Cot_Origin]->scaledToWidth(ScrSize.width(), Qt::SmoothTransformation); 
    }
    else if (m_imgProfile.option & Opt_FitToWidth && m_imgProfile.option & Opt_FitToHeight)
    {
        *divPage.images[Cot_Scaled] = divPage.images[Cot_Origin]->scaled(ScrSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
    }
    else if (m_imgProfile.option & Opt_FitToWidth)
    {
        *divPage.images[Cot_Scaled] = divPage.images[Cot_Origin]->scaledToWidth(ScrSize.width(), Qt::SmoothTransformation); 
    }
    else if (m_imgProfile.option & Opt_FitToHeight)
    {
        *divPage.images[Cot_Scaled] = divPage.images[Cot_Origin]->scaledToHeight(ScrSize.height(), Qt::SmoothTransformation); 
    }

    // Scale the scroll line.
    // If the Opt_ScrollMode change while processing page, the gridForScrolls maybe don't exist,
    // so need to lock here.
    m_mutex.lock(); //>> m_mutex lock
    if (!m_pageQueue.isInitRequest()
        && !m_pageQueue.isReloadRequest()
        && m_imgProfile.option & Opt_ScrollMode)
    {
        float rate = static_cast<float>(divPage.images[Cot_Scaled]->width()) / m_processW;

        for (int i = 0; i < divPage.gridForScrolls.count(); i++)
        {
            divPage.gridForScrolls[i].top *= rate;
            divPage.gridForScrolls[i].bottom *= rate;
        }

        // The bottom of the last grid maybe is not equal to that of image,
        // for the lost of accuracy.
        divPage.gridForScrolls.last().bottom = divPage.images[Cot_Scaled]->height() - 1;
    }
    m_mutex.unlock(); //<< m_mutex unlock

    // Convert to gray iamge.
    divPage.images[Cot_Scaled] = QIplImage::convertToGray(divPage.images[Cot_Scaled]);
    divPage.processed = true;

    Out_Log(__FUNCTION__, COCLASSNAME);
}

/**
 * Analyze image and do margin cutting, degree correction and lines for scroll mode calculation.
 * 
 * @author reach (5/26/2010)
 * 
 * @param divPage: The struct for divided page.
 */
void ProcessThread::imageAnalyze(DivPage& divPage)
{
    In_Log(__FUNCTION__, COCLASSNAME);

    IplImage* cvimgThresh = NULL;
    IplImage* cvimgContour = NULL;
    IplImage* cvimgLine = NULL;
    CvMemStorage* storage = NULL;
    LineList hLines;
    LineList vLines;
    int width = 0;
    int height = 0;
    int linedis = 0;
    float ratio = 0.0f;

    bool hasAligned = false;
    int limit = 2;

    while (limit--)
    {
        /*******************load and threshold the image*******************/
        IplImage* cvimgLoad;

        // Load image.
        if (divPage.images[Cot_Origin]->width() > 600 || divPage.images[Cot_Origin]->height() > 800)
        {
            cvimgLoad = QIplImage::QImageToIplImage(divPage.images[Cot_Origin]->scaled(600, 800, Qt::KeepAspectRatio, Qt::SmoothTransformation), 0);
        }
        else
        {
            cvimgLoad = QIplImage::QImageToIplImage(*divPage.images[Cot_Origin], 0);
        }

        // Initialize properties.
        width = cvimgLoad->width;
        m_processW = width;
        height = cvimgLoad->height;
        linedis = height / 8;
        ratio = (float)divPage.images[Cot_Origin]->height() / height;

        // Threshold image.
        cvimgThresh= cvCreateImage(cvGetSize(cvimgLoad), IPL_DEPTH_8U, 1);
        cvAdaptiveThreshold(cvimgLoad, cvimgThresh, 255, CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY_INV);
        cvReleaseImage(&cvimgLoad);

        // Find out contours.
        CvSeq* contour = NULL;
        storage = cvCreateMemStorage(0);
        int nc = cvFindContours(cvimgThresh, storage, &contour, sizeof(CvContour),
                                CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE, cvPoint(0, 0));

        // Polygon approximation.
        if (nc != 0)
        {
            contour = cvApproxPoly(contour, sizeof(CvContour), storage, CV_POLY_APPROX_DP, 3, 1);
        }

        /*******************analyze contour*******************/
        analyzeContour(contour, hLines, vLines, linedis, cvimgThresh, cvimgContour, cvimgLine);

        /************************degree align**************************/
        m_mutex.lock(); //>> m_mutex lock

        if (!m_pageQueue.isInitRequest()
            && !m_pageQueue.isReloadRequest()
            && m_imgProfile.option & Opt_Align && !hasAligned)
        {
            hasAligned = true;

            if (degreeCorrect(divPage, hLines, vLines, linedis, width, height))
            {
                // Must reanalyze this divPage, because the datas analyzed before are outdated.
                // Release memory.
                cvReleaseImage(&cvimgThresh);
                cvReleaseImage(&cvimgContour);
                cvReleaseImage(&cvimgLine);
                cvReleaseMemStorage(&storage);

                // Repeat this divPage.
                m_mutex.unlock(); //<< m_mutex unlock
                continue;
            }

        }
        m_mutex.unlock(); //<< m_mutex unlock

        /************************convert images**************************/ 
        // Convert the testing images from cvimage to qimage.
#ifndef QT_ARCH_ARM
        divPage.images[Cot_Thresh] = QIplImage::IplImageToQImage(cvimgThresh);
        divPage.images[Cot_Contour] = QIplImage::IplImageToQImage(cvimgContour);
        divPage.images[Cot_Line] = QIplImage::IplImageToQImage(cvimgLine);
#endif      

        // Release memory.
        cvReleaseImage(&cvimgThresh);
        cvReleaseImage(&cvimgContour);
        cvReleaseImage(&cvimgLine);

        /************************cut margin**************************/ 
        m_mutex.lock(); //>> m_mutex lock

        if (!m_pageQueue.isInitRequest()
            && !m_pageQueue.isReloadRequest()
            && m_imgProfile.option & Opt_CutMargin)
        {
            cutMargin(divPage, contour, hLines, vLines, width, height, ratio);
        }
        m_mutex.unlock(); //<< m_mutex unlock

        /*******************find scroll lines**********************/
        m_mutex.lock(); //>> m_mutex lock

        if (!m_pageQueue.isInitRequest()
            && !m_pageQueue.isReloadRequest()
            && m_imgProfile.option & Opt_ScrollMode)
        {
            findScrollLines(divPage, hLines, vLines, width, height);
        } // Scroll Mode
        m_mutex.unlock(); //<< m_mutex unlock

        break;
    } // While limit.

    // Release memory.
    cvReleaseImage(&cvimgThresh);
    cvReleaseImage(&cvimgContour);
    cvReleaseImage(&cvimgLine);
    cvReleaseMemStorage(&storage);

    Out_Log(__FUNCTION__, COCLASSNAME);
}

/**
 * Analyze contour. Extract the horizontal and vertical lines that meet the requirements.
 * 
 * @author reach (5/26/2010)
 * 
 * @param contour: Input. The contour find by OpenCV.
 * @param hLines: Output. List of horizontal lines.
 * @param vLines: Output. List of vertical lines.
 * @param linedis: Input. The minimal length of a line.
 * @param cvimgThresh: Output.
 * @param cvimgContour: Output.
 * @param cvimgLine: Output.
 */
void ProcessThread::analyzeContour(CvSeq* contour, LineList& hLines, LineList& vLines, int& linedis,
                                   IplImage*& cvimgThresh, IplImage*& cvimgContour, IplImage*& cvimgLine)
{
    In_Log(__FUNCTION__, COCLASSNAME);

#ifndef QT_ARCH_ARM
    cvimgContour = cvCreateImage(cvGetSize(cvimgThresh), IPL_DEPTH_8U, 3);
    cvimgLine = cvCreateImage(cvGetSize(cvimgThresh), IPL_DEPTH_8U, 3);
    qsrand(1);
#endif

    for (CvSeq* c = contour; c != NULL; c = c->h_next)
    {
        int contourPointCount = c->total;

        // Skip the empty contour.
        if (contourPointCount < 1)
        {
            continue;
        }

        CvRect rect = cvBoundingRect(c);

        // Skip the too small contour.
        if (rect.width < 30 || rect.height < 30)
        {
            continue;
        }

        //-- Draw contours.
#ifndef QT_ARCH_ARM

        uchar r = qrand()%255;
        uchar g = qrand()%255;
        uchar b = qrand()%255;
        CvScalar color = CV_RGB(r, g, b);

        cvDrawContours(cvimgContour, c,
                       color, color,
                       0, 1, 8, cvPoint(0,0));

        // Draw points of contours.
        for (int i = 0; i < contourPointCount; i++)
        {
            CvPoint* p = CV_GET_SEQ_ELEM(CvPoint, c, i); 
            cvCircle(cvimgContour, *p, 3, color);
        }

#endif

        //-- Generate the lines and draw them.

        // The first step of iteration.
        CvPoint* curPoint = CV_GET_SEQ_ELEM(CvPoint, c, contourPointCount - 1);
        CvPoint* lastPoint = CV_GET_SEQ_ELEM(CvPoint, c, 0);
        int dx = std::abs(curPoint->x - lastPoint->x);
        int dy = std::abs(curPoint->y - lastPoint->y); 

        if (dx >= linedis || dy >= linedis)
        {
#ifndef QT_ARCH_ARM
            cvLine(cvimgLine, *lastPoint, *curPoint, color);
            cvCircle(cvimgLine, *lastPoint, 2, color, 2);
            cvCircle(cvimgLine, *curPoint, 2, color, 2);
#endif            

            // Assign the line to hLines or vLines.
            if (dx >= dy)
            {
                if (lastPoint->x < curPoint->x)
                {
                    hLines.append(Line(*lastPoint, *curPoint)); 
                }
                else
                {
                    hLines.append(Line(*curPoint, *lastPoint)); 
                }
            }
            else
            {
                if (lastPoint->y < curPoint->y)
                {
                    vLines.append(Line(*lastPoint, *curPoint)); 
                }
                else
                {
                    vLines.append(Line(*curPoint, *lastPoint)); 
                }

            }
        }

        // Start iteration.
        for (int i = 1; i < contourPointCount; i++)
        {
            curPoint = CV_GET_SEQ_ELEM(CvPoint, c, i);
            dx = std::abs(curPoint->x - lastPoint->x);
            dy = std::abs(curPoint->y - lastPoint->y); 
            if (dx >= linedis || dy >= linedis)
            {
#ifndef QT_ARCH_ARM
                cvLine(cvimgLine, *lastPoint, *curPoint, color);
                cvCircle(cvimgLine, *lastPoint, 2, color, 2);
                cvCircle(cvimgLine, *curPoint, 2, color, 2);
#endif                  

                // Assign the line to hLines or vLines.
                if (dx >= dy)
                {
                    if (lastPoint->x < curPoint->x)
                    {
                        hLines.append(Line(*lastPoint, *curPoint)); 
                    }
                    else
                    {
                        hLines.append(Line(*curPoint, *lastPoint)); 
                    }
                }
                else
                {
                    if (lastPoint->y < curPoint->y)
                    {
                        vLines.append(Line(*lastPoint, *curPoint)); 
                    }
                    else
                    {
                        vLines.append(Line(*curPoint, *lastPoint)); 
                    }
                }
            }
            lastPoint = curPoint;
        }

    }   // for (CvSeq c)

    Out_Log(__FUNCTION__, COCLASSNAME);
}

struct LineRadian
{
    float radian;
    int count;
};

/**
 * Align the crooked image.
 * 
 * @author reach (5/26/2010)
 * 
 * @param divPage: The struct for divided page.
 * @param hLines: List of horizontal lines.
 * @param vLines: List of vertical lines.
 * @param linedis: The minimal length of a line.
 * @param width: The width of image.
 * @param height: The height of image. 
 * 
 * @return bool: Return ture if the iamge is need to align.
 */
bool ProcessThread::degreeCorrect(DivPage& divPage, LineList& hLines, LineList& vLines,
                                  int linedis, int& width, int& height)
{
    In_Log(__FUNCTION__, COCLASSNAME);

    int linedis_align = linedis * 2;
    int aLCtLeft = width / 8; // left of contrict rect of align lines 
    int aLCtRight = width - aLCtLeft;
    float radianThreshAbs = m_imgProfile.minDegree * RADIAN_PER_DEGREE;
    float maxRadianThreshAbs = 10 * RADIAN_PER_DEGREE;
    float diffRadianThresh = 0.1 * RADIAN_PER_DEGREE;

    // Find the degree the most lines have.
    QVector<LineRadian> radians;

    for (int i = 1; i < vLines.count(); i++)
    {
        CvPoint& p1 = vLines[i].p1;
        CvPoint& p2 = vLines[i].p2;

        // Skip if this vLine do not close to the edges.
        if ((p1.x > aLCtLeft && p1.x < aLCtRight) || p2.y - p1.y < linedis_align)
        {
            continue;
        }

        bool isConnectVLine = false;

        for (int j = 0; j < hLines.count(); j++)
        {
            if (connectType(hLines[j], vLines[i]))
            {
                isConnectVLine = true;
                break;
            }
        }

        // Skip if this vLine do not connect to a hLine.
        if (!isConnectVLine)
        {
            continue;
        }

        float tan = (float)(p1.x - p2.x) / (p1.y - p2.y);
        float radian = std::atan(tan);
        bool isNew = true;

        if (radians.isEmpty())
        {
            LineRadian lineRadian;
            lineRadian.radian = radian;
            lineRadian.count = 1;
            radians.append(lineRadian);
            continue;
        }

        for (int j = 0; j < radians.count(); j++)
        {
            if (std::abs(radians[j].radian - radian) <= diffRadianThresh)
            {
                radians[j].radian = (radians[j].radian + radian) / 2;
                ++radians[j].count;
                isNew = false;
                break;
            }
        }

        if (isNew)
        {
            LineRadian lineRadian;
            lineRadian.radian = radian;
            lineRadian.count = 1;
            radians.append(lineRadian);
        }
    } // for vLines

    float adoptedRadian = 0.0f;
    int maxCount = 0;
    for (int i = 0; i < radians.count(); i++)
    {
        if (radians[i].count > maxCount)
        {
            maxCount = radians[i].count;
            adoptedRadian = radians[i].radian;
        }
    }

    if (std::abs(adoptedRadian) >= radianThreshAbs && std::abs(adoptedRadian) <= maxRadianThreshAbs)
    {
        *divPage.images[Cot_Origin] = divPage.images[Cot_Origin]->transformed(QTransform().rotateRadians(adoptedRadian), Qt::SmoothTransformation); 
        divPage.images[Cot_Origin] = QIplImage::convertToGray(divPage.images[Cot_Origin]);

        Out_Log(__FUNCTION__, COCLASSNAME);
        return true;
    }

    Out_Log(__FUNCTION__, COCLASSNAME);
    return false;
}

/**
 * Cut margin of image.
 * 
 * @author reach (5/26/2010)
 * 
 * @param contour: The contour find by OpenCV. 
 * @param divPage: The struct for divided page.
 * @param hLines: List of horizontal lines.
 * @param vLines: List of vertical lines.
 * @param width: The width of image.
 * @param height: The height of image. 
 * @param ratio: The ratio of the size of original image and that of the scaled image to anlayze. 
 */
void ProcessThread::cutMargin(DivPage& divPage, CvSeq* contour,
                              LineList& hLines, LineList& vLines,
                              int& width, int& height, float& ratio)
{
    In_Log(__FUNCTION__, COCLASSNAME);

    CvRect cvrcCut; // use to cut margin
    cvrcCut.width = 0;
    for (CvSeq* c = contour; c != NULL; c = c->h_next)
    {
        int contourPointCount = c->total;
        if (contourPointCount < 4)
        {
            continue;
        }

        CvRect rect = cvBoundingRect(c);

        if (rect.width < m_imgProfile.ignoreSize || rect.height < m_imgProfile.ignoreSize)
        {
            continue;
        }

        // Calculate the bounding rect of all contours, used to cutting margin.
        if (cvrcCut.width == 0)
        {
            cvrcCut = rect; 
        }
        else
        {
            cvrcCut = cvMaxRect(&cvrcCut, &rect);
        }
    }

    QRect rcCut(cvrcCut.x, cvrcCut.y, cvrcCut.width, cvrcCut.height);
    rcCut.setSize(rcCut.size() * ratio);
    rcCut.moveTopLeft(rcCut.topLeft() * ratio);

    QRect rcMin = divPage.images[Cot_Origin]->rect();
    rcMin.setSize(rcMin.size() * (100 - m_imgProfile.maxMarginPercent) / 100);
    rcMin.moveCenter(divPage.images[Cot_Origin]->rect().center());

    if (rcCut.width() != 0)
    {
        rcCut |= rcMin;
    }
    else
    {
        rcCut = rcMin;
    }

    *divPage.images[Cot_Origin] = divPage.images[Cot_Origin]->copy(rcCut);

    // Cut lines.
    QRect rcLinesCut(rcCut.topLeft() / ratio, rcCut.size() / ratio);
    width = rcLinesCut.width();
    height = rcLinesCut.height();
    m_processW = width;

#ifndef QT_ARCH_ARM
    // Cut the testing images.
    *divPage.images[Cot_Thresh] = divPage.images[Cot_Thresh]->copy(rcLinesCut);
    *divPage.images[Cot_Contour] = divPage.images[Cot_Contour]->copy(rcLinesCut);
    *divPage.images[Cot_Line]= divPage.images[Cot_Line]->copy(rcLinesCut);
#endif

    // Move the lines to fit the cutted image.
    for (int i = 0; i < hLines.count(); i++)
    {
        if (rcLinesCut.contains(hLines[i].p1.x, hLines[i].p1.y) && rcLinesCut.contains(hLines[i].p2.x, hLines[i].p2.y))
        {
            hLines[i].p1.x -= rcLinesCut.x();
            hLines[i].p1.y -= rcLinesCut.y();
            hLines[i].p2.x -= rcLinesCut.x();
            hLines[i].p2.y -= rcLinesCut.y();
        }
        else
        {
            hLines.removeAt(i--);
        }
    }

    for (int i = 0; i < vLines.count(); i++)
    {
        if (rcLinesCut.contains(vLines[i].p1.x, vLines[i].p1.y) && rcLinesCut.contains(vLines[i].p2.x, vLines[i].p2.y))
        {
            vLines[i].p1.x -= rcLinesCut.x();
            vLines[i].p1.y -= rcLinesCut.y();
            vLines[i].p2.x -= rcLinesCut.x();
            vLines[i].p2.y -= rcLinesCut.y();
        }
        else
        {
            vLines.removeAt(i--);
        }
    }

    Out_Log(__FUNCTION__, COCLASSNAME);
}

struct YLine
{
    int top;
    int bottom;
};

typedef QList<YLine> YLineList;

/**
 * Find the bounding lines for scroll mode.
 * 
 * @author reach (5/26/2010)
 * 
 * @param divPage: The struct of divided page.
 * @param hLines: List of horizontal lines.
 * @param vLines: List of vertical lines.
 * @param width: The width of image.
 * @param height: The height of image.
 */
void ProcessThread::findScrollLines(DivPage& divPage, LineList& hLines, LineList& vLines, int& width, int& height)
{
    In_Log(__FUNCTION__, COCLASSNAME);

    // Mapping the lines to y-axis.
    YLineList yLines;

    for (int i = 0; i < hLines.count(); i++)
    {
        bool isConnectVLine = false;

        if (hLines[i].p1.x - 0 <= 3 || width - 1 - hLines[i].p2.x <= 3)
        {
            isConnectVLine = true;
        }

        for (int j = 0; j < vLines.count(); j++)
        {
            if (connectType(hLines[i], vLines[j]) || isConnectVLine)
            {
                isConnectVLine = true;
                break;
            }
        }

        // Skip if this hLine do not connect to a vLine.
        if (!isConnectVLine)
        {
            continue;
        }

        CvPoint& p1 = hLines[i].p1;
        CvPoint& p2 = hLines[i].p2;
        YLine yLine;
        yLine.top = p2.y + (0 - p2.x) * (p1.y - p2.y) / (p1.x - p2.x);
        yLine.bottom = p1.y + (width - p1.x) * (p2.y - p1.y) / (p2.x - p1.x);

        if (yLine.top > yLine.bottom)
        {
            int temp = yLine.top;
            yLine.top = yLine.bottom;
            yLine.bottom = temp;
        }

        yLines.append(yLine);
    }

    // Sort the y-axis mapping lines by top.
    for (int i = 0; i < yLines.count(); i++)
    {
        for (int j = i + 1; j < yLines.count(); j++)
        {
            if (yLines[i].top > yLines[j].top)
            {
                YLine temp = yLines[i];
                yLines[i] = yLines[j];
                yLines[j] = temp;
            }
        }
    }

    GridForScroll curGrid;
    curGrid.top = 0;
    int potentialTop;
    int nextStep = 1; // use to indicate whether top or bottom to find next step
    int minGridH = height / 8;
    int minSpace = 5;
    int maxSpace = minGridH / 3;

    for (int i = 0; i < yLines.count(); i++)
    {
        int top = yLines[i].top;
        int bottom = yLines[i].bottom;

        // Find bottom.
        if (nextStep == 1)
        {
            if (top - curGrid.top < minGridH)
            {
                continue;
            }
            curGrid.bottom = bottom;
            potentialTop = top;
            divPage.gridForScrolls.append(curGrid);
            nextStep = 0;
        }
        // Find top.
        else
        {
            if (top - potentialTop < minSpace)
            {
                continue;
            }

            if (top - potentialTop > maxSpace)
            {
                curGrid.top = potentialTop - 15;
                --i;
                nextStep = 1;
                continue;
            }

            curGrid.top = top - 15;
            nextStep = 1;
        }
    } // for yLines

    // Deal with the end.
    if (nextStep == 1)
    {
        if (height-1 - curGrid.top < minGridH)
        {
            divPage.gridForScrolls.last().bottom = height - 1;
        }
        else
        {
            curGrid.bottom = height - 1;
            divPage.gridForScrolls.append(curGrid);
        }
    }
    else
    {
        if (height-1 - curGrid.bottom < minGridH)
        {
            divPage.gridForScrolls.last().bottom = height - 1;
        }
        else
        {
            curGrid.top = curGrid.bottom;
            curGrid.bottom = height - 1;
            divPage.gridForScrolls.append(curGrid);
        }
    }

    combineScrollLines(vLines, divPage.gridForScrolls);

#ifndef QT_ARCH_ARM
    // Draw the scroll lines.
    divPage.images[Cot_Scroll] = new QImage(width + 10, height + 10, QImage::Format_RGB32);
    divPage.images[Cot_Scroll]->fill(qRgb(0, 0, 0));
    QPainter painter(divPage.images[Cot_Scroll]);
    int gridW = width/2;
    for (int i = 0; i < divPage.gridForScrolls.count(); i++)
    {
        uchar r = qrand()%255;
        uchar g = qrand()%255;
        uchar b = qrand()%255;
        painter.setPen(qRgb(r, g , b));
        GridForScroll& grid = divPage.gridForScrolls[i];
        painter.drawRect(5, grid.top, gridW += 10, grid.bottom - grid.top);
    }
#endif

    Out_Log(__FUNCTION__, COCLASSNAME);
}

/**
 * Check whether the grid and the vLine is cross. 
 * 
 * @author reach (5/26/2010)
 * 
 * @param grid: Grid for scroll mode. 
 * @param vLine: Vertical line. 
 * 
 * @return bool: Return true if the grid and the vLine is cross.
 */
bool ProcessThread::isGridCrossVLine(GridForScroll& grid, Line& vLine)
{
    int center = (grid.top + grid.bottom) / 2;
    return(center >= vLine.p1.y && center <= vLine.p2.y);
}

/**
 * Combine the scroll grids which is cross width a same vertical line.
 * 
 * @author reach (5/26/2010)
 * 
 * @param vLines: List of vertical lines.
 * @param grids: List of scroll grids.
 */
void ProcessThread::combineScrollLines(LineList& vLines, GridForScrollVec& grids)
{
    In_Log(__FUNCTION__, COCLASSNAME);

    for (int i = 0; i < vLines.count(); i++)
    {
        int iStart;
        int iEnd;
        for (iStart = 0; iStart < grids.count(); iStart++)
        {
            if (isGridCrossVLine(grids[iStart], vLines[i]))
            {
                break;
            }
        }

        for (iEnd = iStart + 1; iEnd < grids.count(); iEnd++)
        {
            if (!isGridCrossVLine(grids[iEnd], vLines[i]))
            {
                break;
            }
        }
        --iEnd;

        if (iEnd - iStart >= 1)
        {
            grids[iStart].bottom = grids[iEnd].bottom;
            grids.remove(iStart + 1, iEnd - iStart);
        }
    }

    Out_Log(__FUNCTION__, COCLASSNAME);
}

#define IS_POINT_CONNECT(p1, p2) (std::abs((p1).x - (p2).x) <= 3 && std::abs((p1).y - (p2).y) <= 3)

/**
 * Get the connect type of a horizontal line and a vertical line.
 * 
 * @author reach (5/26/2010)
 * 
 * @param hLine: Horizontal line.
 * @param vLine: Vertical line.
 * 
 * @return int: The connect type number.
 */
int ProcessThread::connectType(Line& hLine, Line& vLine)
{
    // left top
    if (IS_POINT_CONNECT(hLine.p1, vLine.p1))
    {
        return 1;
    }
    // right top
    if (IS_POINT_CONNECT(hLine.p2, vLine.p1))
    {
        return 2;
    }
    // right bottom
    if (IS_POINT_CONNECT(hLine.p2, vLine.p2))
    {
        return 3;
    }
    // left bottom
    if (IS_POINT_CONNECT(hLine.p1, vLine.p2))
    {
        return 4;
    }
    return 0;
}

/**
 * Process of the processing thread.
 * 
 * @author reach (5/26/2010)
 */
void ProcessThread::run()
{
    In_Log(__FUNCTION__, COCLASSNAME);

    forever{
        Page* page;

        m_restart = false;

        if (m_abort)
        {
            Out_Log(__FUNCTION__, COCLASSNAME);
            return; 
        }

        m_mutex.lock(); //>> m_mutex lock
        m_pageQueue.updateQueue();

        while ((page = m_pageQueue.getUnprocess(m_isPrev)) != NULL)
        {
            m_mutex.unlock(); //<< m_mutex unlock

            //-- lode page
            if (!page->loaded && page->needed)
            {
                loadPage(*page);
            }

            if (m_abort)
            {
                Out_Log(__FUNCTION__, COCLASSNAME);
                return; 
            }

            m_mutex.lock(); //>> m_mutex lock
            if (m_pageQueue.isInitRequest() || m_pageQueue.isReloadRequest())
            {
                goto CONTINUE_NEXT;
            }
            m_mutex.unlock(); //<< m_mutex unlock

            //-- process each divided page
            if (m_request)
            {
                m_iDivCount = page->divCount;
                m_iDivPage = CO_MIN(m_iDivPage, m_iDivCount - 1);
                if (m_iDivPage == 0)
                {
                    m_isPrev = false;
                }
                else
                {
                    m_isPrev = true;
                }
            }

            /// latter first
            if (m_isPrev)
            {
                for (int iDivPage = page->divCount - 1; iDivPage >= 0; iDivPage--)
                {
                    DivPage& divPage = *page->divPage[iDivPage];

                    if (!divPage.processed && page->needed)
                    {
                        processPage(divPage);
                    }

                    if (m_abort)
                    {
                        Out_Log(__FUNCTION__, COCLASSNAME);
                        return; 
                    }

                    m_mutex.lock(); //>> m_mutex lock
                    if (m_pageQueue.isInitRequest() || m_pageQueue.isReloadRequest())
                    {
                        goto CONTINUE_NEXT;
                    }

                    if (m_request && page->iPage == m_iPage && iDivPage == m_iDivPage)
                    {
                        m_request = false;
                        emit SendPage(&divPage);
                    }
                    m_mutex.unlock(); //<< m_mutex unlock

                    if (m_abort)
                    {
                        Out_Log(__FUNCTION__, COCLASSNAME);
                        return; 
                    }
                } // for
            }
            /// former first
            else
            {
                for (int iDivPage = 0; iDivPage < page->divCount; iDivPage++)
                {
                    DivPage& divPage = *page->divPage[iDivPage];

                    if (!divPage.processed && page->needed)
                    {
                        processPage(divPage);
                    }

                    if (m_abort)
                    {
                        Out_Log(__FUNCTION__, COCLASSNAME);
                        return; 
                    }

                    m_mutex.lock(); //>> m_mutex lock
                    if (m_pageQueue.isInitRequest() || m_pageQueue.isReloadRequest())
                    {
                        goto CONTINUE_NEXT;
                    }

                    if (m_request && page->iPage == m_iPage && iDivPage == m_iDivPage)
                    {
                        m_request = false;
                        emit SendPage(&divPage);
                    }
                    m_mutex.unlock(); //<< m_mutex unlock

                    if (m_abort)
                    {
                        Out_Log(__FUNCTION__, COCLASSNAME);
                        return; 
                    }
                } // for
            } // if m_isPrev

            if (m_abort)
            {
                Out_Log(__FUNCTION__, COCLASSNAME);
                return; 
            }

            m_mutex.lock(); //>> m_mutex lock
            CONTINUE_NEXT:
            m_pageQueue.updateQueue();
        } // while
        m_mutex.unlock(); //<< m_mutex unlock

        m_mutex.lock(); //>> m_mutex lock
        if (!m_restart)
        {
            m_condition.wait(&m_mutex); 
        }
        m_restart = false;
        m_mutex.unlock(); //<< m_mutex unlock
    }

    Out_Log(__FUNCTION__, COCLASSNAME);
}




